package com.nuonuo.config;

import com.alibaba.fastjson2.JSON;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.CollectionUtils;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author duan
 * @version 1.0
 * @date 2023/10/25 15:39
 */
public class RedisUtils {

    /**
     * RedisTemplate
     */
    private final RedisTemplate redisTemplate;


    /**
     * 字符集
     */
    private final Charset charset = Charset.forName("UTF-8");

    private final static Integer EXPIRE_TIME = 60*60*12;

    public RedisUtils(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }


    /**
     * mget
     * @params
     */
    public List<String> mget(List<String> s) {
        List redisStr =  redisTemplate.opsForValue().multiGet(s);
        List<String> strings=new ArrayList<>();
        for(Object reds:redisStr){
            String s1=new String((byte[]) reds);
            strings.add(s1);
        }
        return strings;
    }


    /**
     * mset
     */
    public void mset(Map map) {
        redisTemplate.opsForValue().multiSet(map);
    }

    /**
     * 将字节数组反序列化为字符串
     */
    public String deserialize(byte[] bytes) {
        return (bytes == null ? null : new String(bytes, charset));
    }

    /**
     * 设置一个key的value值
     */
    public void set(String key, String value) {
        set(key, value.getBytes(charset));
    }

    /**
     * 设置一个key的value值
     */
    public void set(String key, byte[] value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 删除缓存
     *
     * @param keys
     */
    public void delKeys(List<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 设置一个key的value值，并设置过期时间
     */
    public void set(String key, String value, long timeout, TimeUnit unit) {
        set(key, value.getBytes(charset), timeout, unit);
    }

    /**
     * 设置一个key的value值，并设置过期时间
     */
    public void set(String key, byte[] value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }



    /**
     * 1 if the key was set
     * 0 if the key was not set
     * 会出现停机key永久存在的情况，不推荐使用
     * @param key
     * @param value
     * @param timeout
     * @param unit
     * @return
     */
    public Boolean setNx(String key, String value, long timeout, TimeUnit unit) {
        boolean isExist = redisTemplate.opsForValue().setIfAbsent(key, value.getBytes(charset));
        if (isExist) {
            redisTemplate.expire(key, timeout, unit);
            return true;
        }
        return false;
    }

    /**
     * 设置一个key的value值增加delta,value需为number
     * @param key
     * @param delta
     * @param
     * @param
     * @return
     */
    public Long increment(String key, Long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 获取key的value值
     */
    public String get(String key) {
        byte[] bytes = (byte[]) redisTemplate.opsForValue().get(key);
        return (bytes == null) ? null : new String(bytes, charset);
    }

    /**
     * 获取key的value值
     */
    public byte[] getBytes(String key) {
        return (byte[]) redisTemplate.opsForValue().get(key);
    }

    /**
     * 查询一个key是否存在
     */
    public boolean exist(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 删除key
     */
    public void del(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 删除key
     */
    public void del(Collection<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 设置key的过期时间
     *
     * @return true-成功设置过期时间, false-key不存在或者不能设置过期时间
     */
    public boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 设置过期时间点
     * @param key
     * @param expireDate
     * @return
     */
    public boolean expireAt(String key, Date expireDate) {
        return redisTemplate.expireAt(key, expireDate);
    }


    /**
     * 获取队列的长度
     */
    public Long llen(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 从队列的右边（尾部）插入
     */
    public Long rpush(String key, String value) {
        return redisTemplate.opsForList().rightPush(key, value.getBytes(charset));
    }

    /**
     * 从队列的左边（头部）插入
     */
    public Long lpush(String key, String value) {
        return redisTemplate.opsForList().leftPush(key, value.getBytes(charset));
    }

    /**
     * 从队列的左边（头部）弹出
     */
    public String lpop(String key) {
        byte[] bytes = (byte[]) redisTemplate.opsForList().leftPop(key);
        return (bytes == null) ? null : new String(bytes, charset);
    }

    /**
     *获取指定范围的列表
     */
    public List<String> range(String key, long start, long end) {
        List<byte[]> bytes ;
        if(llen(key) > 0) {
            bytes = redisTemplate.opsForList().range(key, start, end);
            List<String> values = new ArrayList<>(bytes.size());
            bytes.forEach(info -> values.add(new String(info, charset)));
            return values;
        }
        return null;
    }


    public List<String> range_(String key, long start, long end) {
        if(llen(key) > 0) {
            List list =  redisTemplate.opsForList().range(key, start, end);
            List<String> list1 = new ArrayList<>();
            for (Object o:list){
                list1.add(new String((byte[])o));
            }
            return list1;
        }
        return null;
    }

    /**
     * 添加元素到集合中
     */
    public Long sadd(String key, String value) {
        return redisTemplate.opsForSet().add(key, (Object)value.getBytes(charset));
    }

    /**
     * 获取有序SET集合的大小
     */
    public long sSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 集合批量添加，若集合不存在，自动新增集合
     * @param key       集合名
     * @param values
     * @return
     */
    public Long sadd(String key, List<String> values) {
        byte[][] arrs = new byte[values.size()][];
        for (int i = 0; i < values.size(); i++) {
            arrs[i] = values.get(i).getBytes(charset);
        }
        return redisTemplate.opsForSet().add(key, arrs);
    }

    /**
     * 判断某个字符串集合中是否有某个元素，若集合不存在，返回false
     * @param key       集合名
     * @return
     */
    public boolean scontains(String key, String value) {
        return redisTemplate.opsForSet().isMember(key, value.getBytes(charset));
    }

    public Set<String> smembers(String key) {
        Set<byte[]> values = redisTemplate.opsForSet().members(key);
        Set<String> valueString = new HashSet<>(values.size());
        if (!CollectionUtils.isEmpty(values)) {
            values.forEach(value->{
                valueString.add(new String(value, charset));
            });
        }
        return valueString;
    }


    /**
     * 获取集合中元素数量
     */
    public Long scard(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 从集合中删除元素
     */
    public Long srem(String key, String value) {
        return redisTemplate.opsForSet().remove(key, (Object)value.getBytes(charset));
    }

    /**
     * 添加元素到有序SET集合中
     */
    public boolean zsetAdd(String key, String value, double score) {
        return redisTemplate.opsForZSet().add(key, value.getBytes(charset), score);
    }


    /**
     * 获取有序SET集合的大小
     */
    public long zsetSize(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }

    /**
     * 删除有序SET集合中对应的value,value可以为多个值
     */
    public long zsetDelete(String key, String values) {
        return redisTemplate.opsForZSet().remove(key, values.getBytes(charset));
    }

    /**
     * 模糊查询指定的key
     * @param pattern
     * @return
     */
    public Set<String> mathchKey(String pattern){
        return redisTemplate.keys(pattern);
    }

    /**
     * 执行
     * @param sessionCallback 回调
     * @return 回调结果
     */
    public List<Object> exce(SessionCallback<Object> sessionCallback) {
        return (List<Object>) redisTemplate.execute(sessionCallback);
    }

    public void convertAndSend(String channel, String message) {
        redisTemplate.convertAndSend(channel, message.getBytes(charset));
    }

    /**
     * 设置redis key
     * @param prefix
     * @param objects
     * @return
     */
    public String getKey(String prefix, Object... objects) {
        StringBuilder sb = new StringBuilder(64);
        sb.append(prefix);

        for (Object object : objects) {
            sb.append(":").append(object);
        }

        return sb.toString();
    }

    /**
     * hash 批量插入元素
     */
    public void hmset(String key,Map map){
        redisTemplate.opsForHash().putAll(key,map);
    }

    /**
     * hash插入元素
     * @param key
     * @param filed
     * @param value
     */
    public void hset(String key,String filed,String value){
        redisTemplate.opsForHash().put(key,filed,value);
    }

    /**
     * hash查询元素
     */

    public <T> T hget(String key,String filed,Class<T> clazz){
        try {
            Object o = redisTemplate.opsForHash().get(key, filed);
            if (o instanceof String && ((String) o).startsWith("{") && ((String) o).endsWith("}")) {
                return JSON.parseObject(o.toString(), clazz);
            } else {
                return (T)o;
            }
        }catch (Exception e) {
//            LogUtils.outLogError(filed, key, LogCodeConstant.REDIS_ERROR,
//                    LogCodeConstant.EXCEPTION, "redis:hget方法hash查询元素处理异常！", e);
            return null;
        }
    }

    /**
     * hash查询所有元素
     */

    public <T> Map<String, T> hgetAll(String key, Class<T> clazz) {
        try {
            Map<Object, Object> result = redisTemplate.opsForHash().entries(key);
            if (result != null && !result.isEmpty()) {
                Map<String, T> resultMap = new HashMap<>(result.size());
                for (Map.Entry<Object, Object> entry : result.entrySet()) {
                    String field = entry.getKey().toString();
                    Object value = entry.getValue();
                    if (value instanceof String && ((String) value).startsWith("{") && ((String) value).endsWith("}")) {
                        resultMap.put(field, JSON.parseObject(value.toString(), clazz));
                    } else {
                        resultMap.put(field, (T) value);
                    }
                }
                return resultMap;
            }
        } catch (Exception e) {
            return null;
        }
        return null;
    }


    /**
     * hash  模糊查询
     * @param key
     * @param prefix
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Map<String,T> hscan(String key,String prefix,Class<T> clazz){
        Map<String, T> map = new HashMap<>();
        try {
            Cursor<Map.Entry<Object, Object>> scan = redisTemplate.opsForHash()
                    .scan(key, ScanOptions.scanOptions().match(prefix + "_*").count(100).build());
            while (scan.hasNext()) {
                Map.Entry<Object, Object> entry = scan.next();
                map.put(entry.getKey().toString(), JSON.parseObject(entry.getValue().toString(), clazz));
            }
            scan.close();
        }catch (Exception e){
            return null;
        }
        return map;
    }

    /**
     * 删除hash表元素
     */
    public Long hdelete(String key,String filed){
        return redisTemplate.opsForHash().delete(key,filed);
    }

    /**
     * 随机获取不同的set数据
     */
    public List<String> distinctRandomMembers(String key,Long queryCount){
        if (redisTemplate.opsForSet().size(key) <= 0) {
            return null;
        }
        Set<byte[]> bytesList = redisTemplate.opsForSet().distinctRandomMembers(key,queryCount);
        List<String> lists = new ArrayList<>(bytesList.size());
        bytesList.forEach(bytes -> lists.add(new String(bytes,charset)));
        return lists;
    }

    /**
     * 从右边批量插入list集合数据
     */
    public void rpushAll(String key , List<String> values){
        List<byte[]> bytes = new ArrayList<>(values.size());
        values.forEach(value -> bytes.add(value.getBytes(charset)));
        redisTemplate.opsForList().rightPushAll(key,bytes);
    }

    /**
     * 批量删除set中的数据
     */
    public void batchSrem(String key,List<String> values){
        if (redisTemplate.opsForSet().size(key) > 0 && !CollectionUtils.isEmpty(values)) {
            List<byte[]> bytes = new ArrayList<>(values.size());
            values.forEach(value -> bytes.add(value.getBytes(charset)));
            redisTemplate.opsForSet().remove(key, bytes.toArray());
        }
    }

    /**
     * 从redis中获取key对应的过期时间;
     * 如果该值有过期时间，就返回相应的过期时间;
     * 如果该值没有设置过期时间，就返回-1;
     * 如果没有该值，就返回-2;
     * @param key key值
     * @return {@link Long}
     */
    public Long getExpire(String key) {
        return redisTemplate.opsForValue().getOperations().getExpire(key);
    }



    /***
     * 获取锁
     * @param key 分布式锁键
     * @param value uuid
     * @return 锁住返回true
     */
    public boolean lock(String key, String value) {

        int lockNum = 5;
        // 连续获取锁失败5次，则返回失败
        while (lockNum > 0) {
            redisTemplate.setEnableTransactionSupport(true);
            redisTemplate.multi();
            redisTemplate.opsForValue()
                    .setIfAbsent(key, value);
            redisTemplate.expire(key,30000L, TimeUnit.MILLISECONDS);
            List exec = redisTemplate.exec();

            if (!CollectionUtils.isEmpty(exec)) {
                return true;
            }
            lockNum--;
        }

        return false;
    }


    /**
     * 描述:使用LUA脚本，更安全释放锁
     *
     * @param key key
     * @param val val
     * @date 2021-08-30
     */
    public void safedUnLock(String key, String val) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end ";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        redisTemplate.execute(redisScript, Collections.singletonList(key), val);

    }

    /**
     * 添加zset
     *
     * @param key
     * @param value
     * @param score
     */
    public void zadd(String key, String value, double score) {
        redisTemplate.opsForZSet().add(key, value.getBytes(charset), score);
    }

    /**
     * 获取指定分数范围的值
     *
     * @param key
     * @param min
     * @param max
     * @param offset
     * @param count
     * @return
     */
    public Set<byte[]> rangeByScore(String key, double min, double max, long offset, long count) {
        Set<byte[]> set = redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
        return set;
    }

    /**
     * 移除
     *
     * @param key
     * @param value
     */
    public void zremove(String key, String value) {
        redisTemplate.opsForZSet().remove(key, value.getBytes(charset));
    }


    /**
     * 描述：获取随机过期时间，范围60*60*12~60*60*48 单位：s
     *
     * @return {@link Integer}
     * @author wangchen_java@nnuo.com
     * @date 2021/6/18 13:31
     */
    public static Integer getExpireTime(){
        return EXPIRE_TIME + (int)(Math.random()*(EXPIRE_TIME*3));
    }

}
